【数据分享】如何获取全国矢量地铁站点与线路数据?
前言
在【数读城事】学习交流群与群友交流的过程中我注意到地铁数据的获取并不容易,根据传统的POI数据绘制地铁线路费时费力(如:某一地铁站的多个出口,某个在建没有投入使用的站点,一个站点有时是多个线路的中转站)。今天在偶然间我翻阅了百度地图的地铁图JavaScript API,发现通过这个接口可以获取网页版的地铁图,近一步发现该数据是JSON格式的,可以抓取,便编写了一个py脚本获取了全国的地铁站点与线路数据,并写下这篇文章,希望能为大家提供方便。
值得一提的是,本人并不会JAVA,自学过一点点的Python,发现这个接口完全是偶然,代码也有很多不足的地方,结论中指出了一些问题,希望同学们多多批评指正。另外,AK(有浏览器端和服务器端的区别,详情见百度开放平台)我就不放出来了,大家改成自己的就行了。
一、服务文档
百度开放平台
http://lbsyun.baidu.com/
地铁图JavaScript API
http://lbsyun.baidu.com/index.php?title=subway
示例DEMO
http://lbsyun.baidu.com/jsdemo.htm#subway0_0
二、分析过程
2.1 HTML文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>地铁图展示</title>
<script type="text/javascript" src="https://api.map.baidu.com/api?type=subway&v=1.0&ak=您的AK"></script>
<style type="text/css">
#container{height:100%}
</style>
</head>
<body>
<div id="container"></div>
<script type="text/javascript">
/**
* 从所有城市列表中获取北京信息
* 结果格式
* {
* keyword: 'zhengzhou',
* name: '郑州',
* citycode: '268'
* }
*/
/* globals BMapSub */
var subwayCityName = '郑州';
var list = BMapSub.SubwayCitiesList;
var subwaycity = null;
for (var i = 0; i < list.length; i++) {
if (list[i].name === subwayCityName) {
subwaycity = list[i];
break;
}
}
// 获取郑州地铁数据-初始化地铁图
var subway = new BMapSub.Subway('container', subwaycity.citycode);
subway.setZoom(0.5);
</script>
</body>
</html>
2.2 调用后得到城市地铁图
2.3 分析XHR得到地铁站点与线路JSON
网址示例:
https://api.map.baidu.com/?qt=subways&c=268&format=json&ak=您的AK&v=3.0&from=jsapi&callback=BMapSub._rd._cbk78501
268为郑州城市编码,其他城市可查阅该附件。(附件在分享的文件夹内)
📎BaiduMap_cityCode_1102.txt
删除callback进入网址后得到JSON数据
{
"result": {
"type": 920,
"error": 0
},
"subways": {
"l": [
{
"p": [
{
"p_xmlattr": {
"sid": "河南工业大学",
"lb": "河南工业大学",
"x": -578,
"y": -432.9,
"rx": 8,
"ry": -10,
"st": true,
"ex": false,
"iu": true,
"rc": false,
"slb": true,
"ln": "郑州市|地铁1号线",
"int": "2",
"px": 12640317.93,
"py": 4116581.76,
"uid": "f235db2f54efea3bf942b28b"
}
},
{
"p_xmlattr": {
"sid": "郑大科技园",
"lb": "郑大科技园",
......
分析网址可以发现1条线路中对应多个地铁站点。
2.4 矢量化思路
2.4.1 HTML转PNG后加载到ARCGIS中描图。
缺陷:描图有误差,加载后需配置,截大图需要借助工具。
2.4.2 JSON数据中点与线抓取至CSV,点集转线。
抓取时构建线路字段,Point To Line(点集转线)工具连接即可。
三、代码实现
3.1 运行代码
3.1.1 导入包
transCoordinateSystem来自于@wandergis
github地址👇
https://github.com/wandergis/coordTransform_py
调用了百度地理坐标转GPS坐标的方法
import requests
import csv
import pandas as pd
import json
import math
from transCoordinateSystem import bd09_to_wgs84
3.1.2 设置基本信息
base_infor = []
lon_lat_infor = []
header = ['num','x','y','poi_name','route_name','route_num','bd_x','bd_y','wgs_x','wgs_y']
num = 0
bmap_key = '浏览器端AK'
ak = '服务器端AK'
3.1.3 定义一个坐标转换方法
def transform_coordinate_batch(coordinates):
req_url = 'http://api.map.baidu.com/geoconv/v1/?coords='+coordinates+'&from=6&to=5&ak=' + bmap_key
data = requests.get(req_url)
data = data.text
data = json.loads(data)
coords = ''
if data['status'] == 0:
result = data['result']
if len(result) > 0:
for res in result:
lng = str(res['x'])[:10]
lat = str(res['y'])[:9]
coords = coords + ";" + lng + "," + lat
return coords.strip(";")
3.1.4 抓取数据:
在这里的268为郑州市的百度编码,如需其他城市的编码,可见百度开放平台或直接查看文件BaiduMap_cityCode_1102.txt。
# 抓取基本数据
city = '268' #268为郑州,城市编码见 BaiduMap_cityCode_1102.txt
url = 'https://api.map.baidu.com/?qt=subways&c=%s&format=json&ak=%s&v=3.0&from=jsapi'%(city,ak)
res = requests.get(url)
json_res = res.json()
subways = json_res['subways']['l']
route_num = 0
for route in subways:
route_num += 1
route_name = route['l_xmlattr']['lid']
for route_poi in route['p']:
poi = route_poi['p_xmlattr']
poi_name = poi['sid']
if poi_name != '':
num += 1
x = poi['px']
y = poi['py']
coordinates = coordinates +str(x)+','+str(y)+';'
base_infor.append([num,x,y,poi_name,route_name,route_num])
3.1.5 坐标转换
# 坐标转换
# 调用函数与接口转换米制坐标为百度坐标
coordinates_list_num = math.ceil(num/90) #确定调用接口的次数,一次传入坐标对90个。
for item in range(coordinates_list_num):
zuobiao = ''
for i in numlist_coor[90*(item):90*(item+1)]:
zuobiao = zuobiao + i +';'
mi_zuobiao = zuobiao[:-1]
result = transform_coordinate_batch(mi_zuobiao)
result = result.split(';')
mi_list = mi_zuobiao.split(';')
for j1 in mi_list:
mi_x = j1.split(',')[0]
mi_y = j1.split(',')[1]
bd_x = result[mi_list.index(j1)].split(',')[0]
bd_y = result[mi_list.index(j1)].split(',')[1]
# print(bd_x,bd_y) 打印出来看看数据怎么样
#调用transCoordinateSystem实现bd_09转换为WGS84坐标系
coord_wgs84 = bd09_to_wgs84(float(bd_x), float(bd_y))
lon = str(coord_wgs84[0])[:10]
lat = str(coord_wgs84[1])[:9]
# print(lon,lat)
lon_lat_infor.append([bd_x,bd_y,lon,lat])
3.1.6 存储至CSV
# 合并结果:base_infor与lon_lat_infor转为DataFrame后合并,写入subway.csv。
left = pd.DataFrame(base_infor)
right = pd.DataFrame(lon_lat_infor)
end_result = pd.concat([left,right],axis=1)
print(end_result)
pd.DataFrame(end_result).to_csv('subway_zhengzhou.csv',mode ='w',index =False,encoding='gbk',header=header)
3.2 代码运行结果
3.2.1 print一下看看end_result是什么?
Windows PowerShell
版权所有 (C) 2016 Microsoft Corporation。保留所有权利。
PS E:\python> & "C:/Program Files/Python37/python.exe" e:/python/subway.py
0 1 2 3 4 5 0 1
0 1 12640317.93 4116581.76 河南工业大学 地铁1号线 1 113.536042 34.827940
1 2 12640267.80 4115257.87 郑大科技园 地铁1号线 1 113.535598 34.818137
2 3 12640225.76 4114004.12 郑州大学 地铁1号线 1 113.535229 34.808853
3 4 12640185.96 4112164.74 梧桐街 地铁1号线 1 113.534887 34.795235
4 5 12640184.88 4110735.11 兰寨 地铁1号线 1 113.534887 34.784654
.. .. ... ... ... ... .. ... ...
90 91 12674935.63 4083938.18 康平湖 城郊线 4 113.847217 34.586106
91 92 12675649.65 4082749.82 兰河公园 城郊线 4 113.853686 34.577188
92 93 12675659.47 4081569.23 恩平湖 城郊线 4 113.853783 34.568424
93 94 12675402.83 4079194.33 综合保税区 城郊线 4 113.851480 34.550827
94 95 12674873.23 4076049.16 新郑机场 城郊线 4 113.846695 34.527542
[95 rows x 8 columns]
3.2.2 储存的CSV是什么样子的?
3.2.3 加载到GIS中的郑州地铁数据与地铁图JavaScript的区别
四、总结
本文无意中发现了百度地图中地铁数据的接口,并通过编写python脚本实现了抓取,导入GIS平台中发现整体状况较好,但同时也遇到了一些问题。
问题1:GIS中的地理坐标转换太麻烦了!坐标转换是文本的一大难点,虽然编写了脚本,但由于本人水平有限,代码仍有较多冗余。
五、数据分享
防止没看到,再说一遍:
在公众号后台回复“地铁”两个字就会收到数据获取方式,emmmm,就,你们懂的。
那今天就到这里结束啦,欢迎留言讨论。文中的图片未经许可不要随便“引用”。
如果可以的话,希望能够转发分享,点个在看并且点个赞,给个赞赏~~也欢迎规范转载~
也希望大家和我多留言互动啊!(据说这样可以增加我的推送在你的订阅号里出现的概率)
END>
如需全文转载文章、投稿或者合作
可添加微信
(回复超慢!!!)
(不要添加我问各种问题,我大概率不会的==)
(入群请一定要备注入群)
(添加后会在晚上非工作时间通过,请稍安勿躁)
此外,我申请了一个微博账号,虽然现在没啥粉丝==
但我会在上面预告更新的!(还有吐槽和碎碎念)
最后,感谢关注【数读城事】
我会尽量在工作之余努力更新的
使用关键词搜索历史文章请点击【阅读原文】